--- /dev/null
+memo
+info
+
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.bin
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
--- /dev/null
+# RGB(W) LED Interplay Medium dendrite module (for ESP8266)
+
+This is IM denrite module (remote wifi network device) created for [Interplay Medium™](https://interplaymedium.org) project.
+
+![Interplay Medium RGB(W) LED Dendrite](https://repository.interplaymedium.org/RGBW%20Controller/IM_RGBW_LED_dendrite.jpeg)
+
+## Pinouts
+
+For LEDs be sure to add MOSFETs and current limiting resistors appropriately. Wiring scheme will be added probably later.
+
+![ESP8266 Pinout](https://repository.interplaymedium.org/RGBW%20Controller/esppinout_.png)
+![ESP8266 Programming](https://repository.interplaymedium.org/RGBW%20Controller/usbprogram_.png)
+
+## Preparing the building environment
+
+Make sure that you have the environment installed as described at
+
+1. [makeEspArduino.mk](https://github.com/plerup/makeEspArduino.git)
+2. [esp8266 Arduino SDK](https://github.com/esp8266/Arduino)
+
+3. In the *make* script, change path for each variable approprately:
+ MAKE_FILE=....
+ ESP_SDK_ROOT=....
+
+## Change your IM AXOD microserver or AP (router) WIFI login and password
+
+create the file
+ vim ../info
+
+assign SSID and PASSWORD of your local IM AXOD microserver or Access Point in there
+
+ WIFI_SSID="ssid"
+ WIFI_PASS="ssid password"
+
+You can change it later whenewer you want using HTTP interface
+
+## Building
+
+initial buildong and flashing firmware at once
+
+ ./make RGBW_Controller upload
+
+after that you may just build the binary and uload it using remote HTTP interface
+
+ ./make RGBW_Controller
+ curl -F image=@RGBW_Controller.bin -s http://im_<....>.lan/update
+
+## Usage
+
+
+By default dendrite can be reached "im_rgb5" doman
+
+Change it with
+
+ curl "http://im_rgb5?rename=NEWNAME"
+
+Turn on the color and effect
+
+ curl "im_rgb5?rgbw=aadd00&rotate=100"
+
+Other options
+
+ curl "http://im_rgb5/help"
+
+## Todo
+
+This firmware is in progress. Here is a brief list of upcoming changes.
+
+* make default URL as IM_(LAST 4 DIGITS OF MAC ADDRESS)
+* add interface features (save, reset....)
+* state return in 2 variants
+ txt (default)
+ JSON
+* add commands
+ switch (like rgbw=00ff0000) for latch switchers
+ dimm (for dimmer, HW prototype required, 220v)
+ rgbwdef -- save default values in EEPROM, which is returning on reset command
+ rotatedef -- same for rotation
+
+* switching AP/slave, AP by defuault
+ remote access setup (host name, AP/slave, SSID, passw)
+ save in EEPROM
+
+## License
+
+Copyright © 2016 Dmitry Shalnov [interplaymedium.org]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this files except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+
+
+
+
--- /dev/null
+/*
+/*
+ * RGB(W) LED IM Dendrite module (for ESP8266)
+ * Created for Interplaymedium™ project (https://interplaymedium.org)
+ * Copyright © 2016 Dmitry Shalnov [interplaymedium.org]
+ * Licensed under the Apache License, Version 2.0
+*/
+
+#include "../../info"
+
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <EEPROM.h>
+
+#define VERSION "0.1.4"
+
+#define EEPROM_STR_MAX_LEN 16
+#define EEPROM_OTHER_MAX_LEN 8
+
+#define EEPROM_STR 0
+#define EEPROM_STR_FLAG 17 // watch overlap: EEPROM_STR_MAX_LEN
+#define EEPROM_R 18
+#define EEPROM_G 19
+#define EEPROM_B 20
+#define EEPROM_W 21
+#define EEPROM_ROT_H 22
+#define EEPROM_ROT_L 23
+
+// -------------------- PWM settings ----------------------------------------------------
+
+extern "C"{
+ #include "pwm.h" // Includes of Expressif SDK
+}
+
+#define LED1 0
+#define LED2 2
+#define LED3 3
+#define LED4 1
+
+#define PWM_CHANNELS 4
+
+#define PWMSTEPS 255
+#define PWM_PERIOD 5000 // Period of PWM frequency, SDK default: 5000 -> * 200ns ^= 1 kHz
+
+unsigned int CIEL8[ PWMSTEPS ];
+
+int R=0, G=0, B=0, W=0;
+unsigned int Rnew=0, Gnew=0, Bnew=0, Wnew=0;
+unsigned int rotateDelay = 0;
+int signR = 1, signG = 1, signB = 1, signW = 1;
+unsigned int eTimer = 0;
+
+uint32 pwm_duty_init[PWM_CHANNELS]; // PWM initial duty: OFF by default
+
+uint32 io_info[PWM_CHANNELS][3] = {
+
+// MUX, FUNC, PIN
+
+// {PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5, 5}, // D1
+// {PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4, 4}, // D2
+ {PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0, LED1}, // D3 0
+ {PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2, LED2}, // D4 2
+
+ {PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3, LED3}, // RX 3
+ {PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1, LED4}, // TX 1
+
+// {PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14, 14}, // D5
+// {PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12, 12}, // D6
+// {PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13, 13}, // D7
+// {PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15 ,15}, // D8
+
+// D0 - not have PWM :-( 16
+};
+
+// ------------------- server settings --------------------------------------------------
+
+#define HOST_DEFAULT "im_rgb5"
+
+char host[ EEPROM_STR_MAX_LEN ];
+
+const char* ssid = IM_WIFI_SSID;
+const char* password = IM_WIFI_PASS;
+
+ESP8266WebServer server(80);
+
+String HTTPresp;
+
+// ----------------- change current R G B (W) to another RGBW set ----------------------
+
+void changeRGBto( int newR, int newG, int newB, int newW ){
+
+ float stepR, stepG, stepB, stepW;
+ int R2, G2, B2, W2;
+/*
+// int R, G, B, W;
+
+// R = EEPROM.read( 0 );
+// G = EEPROM.read( 1 );
+// B = EEPROM.read( 2 );
+// W = EEPROM.read( 3 );
+*/
+ stepR = (newR - R) / (float)PWMSTEPS;
+ stepG = (newG - G) / (float)PWMSTEPS;
+ stepB = (newB - B) / (float)PWMSTEPS;
+ stepW = (newW - W) / (float)PWMSTEPS;
+
+ for ( unsigned int a = 0; a < PWMSTEPS; a++ ){
+
+ R2 = R + round( stepR * (float)a );
+ G2 = G + round( stepG * (float)a );
+ B2 = B + round( stepB * (float)a );
+ W2 = W + round( stepW * (float)a );
+
+ pwm_set_duty( CIEL8[R2], 0 );
+ pwm_set_duty( CIEL8[G2], 1 );
+ pwm_set_duty( CIEL8[B2], 2 );
+ pwm_set_duty( CIEL8[W2], 3 );
+
+ pwm_start(); // commit
+
+ delay( 500 / PWMSTEPS );
+ }
+}
+
+// ----------- explode for selected substring -----------
+
+String expld( String str, unsigned int numb, char delimiter ){
+
+ unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0;
+
+ lng = str.length();
+
+ for ( a = 0; a < lng; a++ ){
+ if ( str.charAt( a ) == delimiter ) {
+ p2 = p1;
+ p1 = a;
+ if ( cnt == numb ) break;
+ cnt ++;
+ }
+ }
+
+ if ( a == lng ) {
+ p2 = p1;
+ p1 = lng;
+ }
+
+ if ( numb > 0 ) p2 ++;
+
+ return str.substring(p2, p1);
+
+}
+
+unsigned char URIHasArg( String str, String arg ){
+
+
+ unsigned char a = 0, delimiterCnt = 0, lng = str.length();
+
+ for ( a = 0; a < lng; a++ ){
+ if ( str.charAt( a ) == '/' ) delimiterCnt++;
+ }
+
+ for ( a=0; a < 10; a++ ){
+ if ( arg.equals( expld( str, a, '/' ) ) ) return a;
+ }
+ return 0;
+}
+
+// ---------------- EEPROM String r/w -------------------
+
+void EEPROMStrRead( unsigned char addr, char * str ){
+ for (unsigned char a = addr; a < EEPROM_STR_MAX_LEN; a++ ){
+ str[ a ] = EEPROM.read( a );
+ if (str[ a ] == 0) break;
+ }
+}
+
+void EEPROMStrWrite( unsigned char addr, char * str ){
+ unsigned char a = 0;
+ for (a = addr; a < EEPROM_STR_MAX_LEN; a++ ){
+ EEPROM.write( a, str[ a ] );
+ if (str[ a ] == 0) break;
+ }
+ EEPROM.write( a, 0 );
+}
+
+// ---------------- misc ---------------------------------
+
+int str2HEX(const String str) {
+ return strtol( str.c_str(), 0, 16 );
+}
+
+void blink( unsigned char times ){
+ for (unsigned char a = 0; a < times; a++ ){
+ digitalWrite(LED4, HIGH);
+ delay (50);
+ digitalWrite(LED4, LOW);
+ delay (50);
+ }
+}
+
+// --------------- Init --------------------------------
+
+void setup(void) {
+
+ HTTPresp.reserve(800); // lenght of Help message generally
+
+ // calculate lookup array
+
+ for (unsigned int a = 1; a < PWMSTEPS; a++) CIEL8[ a ] = round( pow( PWM_PERIOD, (double)a / (PWMSTEPS-1) ) ); // calculate exponential lookup array for PWM
+ CIEL8[ 0 ] = 0;
+
+ // init LED pins
+
+ pinMode(LED1, OUTPUT);
+ pinMode(LED2, OUTPUT);
+ pinMode(LED3, OUTPUT);
+ pinMode(LED4, OUTPUT);
+
+ digitalWrite(LED1, LOW);
+ digitalWrite(LED2, LOW);
+ digitalWrite(LED3, LOW);
+ digitalWrite(LED4, LOW);
+
+ // PWM inti
+
+ for (uint8_t channel = 0; channel < PWM_CHANNELS; channel++) pwm_duty_init[channel] = 0; // Initial duty -> all off
+ uint32_t period = PWM_PERIOD;
+ pwm_init(period, pwm_duty_init, PWM_CHANNELS, io_info);
+ pwm_start();
+
+ // Serial init
+#if SERIAL == 1
+ Serial.begin(115200);
+ Serial.println();
+ Serial.println("Booting Sketch...0");
+#endif
+
+ // mDNS init
+
+// deviceURI.reserve(7);
+// deviceURI = "im" + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00
+
+ EEPROM.begin( EEPROM_STR_MAX_LEN + EEPROM_OTHER_MAX_LEN );
+
+ if ( EEPROM.read( EEPROM_STR_FLAG ) == 1 ){
+ EEPROMStrRead( EEPROM_STR, host );
+ } else {
+ strcpy( host, HOST_DEFAULT ); // default URI and host name
+ }
+
+ WiFi.hostname( host );
+
+// WiFi.softAP(APssid, APpassword);
+
+// WiFi.mode(WIFI_AP);
+// WiFi.mode(WIFI_AP_STA);
+ WiFi.mode(WIFI_STA);
+ WiFi.begin(ssid, password);
+
+ if (WiFi.waitForConnectResult() == WL_CONNECTED) {
+
+// MDNS.begin( deviceURI.c_str() );
+ MDNS.begin( host );
+
+ // default
+
+ server.onNotFound( []() {
+ server.sendHeader("Connection", "close");
+
+ unsigned int RGBW = 0xff;
+ String param = "";
+ String command = "";
+
+ // parse plain URI arguments TODO: not sure whether we need it, check Plan 9 specifications
+/*
+ param = server.arg("rgbw");
+ if ( param == "" ) {
+ param = URIHasArg( server.uri(), "rgbw" );
+ }
+*/
+ // color change
+
+ if ( server.hasArg("rgbw") ){ // || param != 0
+
+ param = server.arg("rgbw");
+
+ Rnew = str2HEX( param.substring(0, 2) );
+ Gnew = str2HEX( param.substring(2, 4) );
+ Bnew = str2HEX( param.substring(4, 6) );
+ Wnew = str2HEX( param.substring(6, 8) );
+
+ changeRGBto( Rnew, Gnew, Bnew, Wnew );
+
+ R = Rnew;
+ G = Gnew;
+ B = Bnew;
+ W = Wnew;
+ }
+
+ // rotate
+
+ if ( server.hasArg("rotate") ){
+ rotateDelay = str2HEX( server.arg("rotate") );
+ }
+
+ // rename host
+
+ if ( server.hasArg("rename") ) {
+ EEPROMStrWrite( EEPROM_STR, (char *)server.arg("rename").c_str() );
+ EEPROM.write( EEPROM_STR_FLAG, 1 );
+ EEPROM.commit();
+
+ server.sendHeader("Connection", "close");
+ server.send_P(200, "text/plain", PSTR("Done. System rebooting...\n") );
+
+ delay(500);
+ ESP.restart();
+ }
+
+ // ------
+
+ HTTPresp = "host: " + String(host) + ".lan" + "\n";
+ HTTPresp += "URI: " + server.uri() + "\n";
+// HTTPresp += "Arg RGBW: " + String( URIHasArg( server.uri(), "test2" ) ) + "\n"; // URI contains /test2/
+// HTTPresp += "Arg RGBW: " + server.arg("aaaa") + "\n"; // GET or POST params has "aaaa"
+ HTTPresp += "rotate: " + String(rotateDelay) + "\n";
+ HTTPresp += "rgbw: " + String(R) + " " + String(G) + " " + String(B) + " " + String(W) + "\n";
+
+ server.send(200, "text/plain", HTTPresp);
+ });
+
+ // test EEPROM string
+
+ server.on("/eeprom", HTTP_GET, []() {
+
+ char * testStr2 = "asdfghjkl12345";
+
+ server.setContentLength(CONTENT_LENGTH_UNKNOWN);
+ server.send(200, "text/plain", "");
+
+ EEPROMStrRead( EEPROM_STR, testStr2 );
+
+ server.sendContent( testStr2 );
+ server.sendContent( "\n" );
+ });
+
+ // help
+
+ server.on("/help", HTTP_GET, []() {
+
+ HTTPresp = "Interplay Medium ESP8266 LED RGB(W) PWM Controller. Version: " + String(VERSION) + "\n";
+ HTTPresp += "Written by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. \n\n";
+
+ HTTPresp += " rgbw Red Green Blue and White components in hexadecimal form (e.g. ff00ff00)\n";
+ HTTPresp += " rotate Delay of components rotation FX in hexadecimal (e.g. ffff), set 0 to stop\n";
+ HTTPresp += " update Wireless update of firmware (see example below)\n";
+ HTTPresp += " help Send this help\n\n";
+
+ HTTPresp += "Usage: curl http://" + String(host) + ".lan [--data \"rgbw=<hex RGBW>\"] [--data \"rotate=<hex delay, 0 = stop >\"] \n";
+ HTTPresp += " curl http://" + String(host) + ".lan[/rgbw/<hex RGBW>][/rotate/<hex delay>] \n";
+ HTTPresp += "Examples: curl http://" + String(host) + ".lan/?rgbw=00ff00ff&rotate=ff \n";
+ HTTPresp += " curl -F image=@RGBW_Controller.bin " + String(host) + ".lan/update \n";
+
+ server.sendHeader("Connection", "close");
+ server.send( 200, "text/plain", HTTPresp );
+
+ });
+
+ // OTA update
+
+ server.on("/update", HTTP_POST, []() {
+
+ server.sendHeader("Connection", "close");
+ server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
+
+ ESP.restart();
+
+ }, []() {
+
+ HTTPUpload& upload = server.upload();
+
+ if (upload.status == UPLOAD_FILE_START) {
+
+ rotateDelay = 0;
+ blink( 3 );
+
+#if SERIAL == 1
+ Serial.setDebugOutput(true);
+#endif
+ WiFiUDP::stopAll();
+
+#if SERIAL == 1
+ Serial.printf("Update: %s\n", upload.filename.c_str());
+#endif
+ uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
+
+ if (!Update.begin(maxSketchSpace)) { //start with max available size
+#if SERIAL == 1
+ Update.printError(Serial);
+#endif
+ }
+ } else if (upload.status == UPLOAD_FILE_WRITE) {
+ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
+#if SERIAL == 1
+ Update.printError(Serial);
+#endif
+ }
+ } else if (upload.status == UPLOAD_FILE_END) {
+ if (Update.end(true)) { //true to set the size to the current progress
+
+ server.sendHeader("Connection", "close");
+ server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") );
+#if SERIAL == 1
+ Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize);
+#endif
+ } else {
+ server.sendHeader("Connection", "close");
+ server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") );
+#if SERIAL == 1
+ Update.printError(Serial);
+#endif
+ }
+#if SERIAL == 1
+ Serial.setDebugOutput(false);
+#endif
+ }
+
+ yield();
+ });
+
+ // start server
+
+ server.begin();
+ MDNS.addService("http", "tcp", 80);
+#if SERIAL == 1
+ Serial.printf("Ready! Open http://%s.local in your browser\n", host);
+#endif
+
+ } else {
+#if SERIAL == 1
+ Serial.println("WiFi Failed");
+#endif
+ }
+}
+
+void loop(void) {
+
+ server.handleClient();
+ MDNS.update();
+
+ eTimer ++;
+
+ if ( eTimer >= rotateDelay && rotateDelay != 0 ) {
+
+ eTimer = 0;
+
+ R = R + signR;
+ G = G + signG;
+ B = B + signB;
+ W = W + signW;
+
+ if ( R >= PWMSTEPS-1 ) { signR = -1; R = PWMSTEPS-1; }
+ if ( G >= PWMSTEPS-1 ) { signG = -1; G = PWMSTEPS-1; }
+ if ( B >= PWMSTEPS-1 ) { signB = -1; B = PWMSTEPS-1; }
+ if ( W >= PWMSTEPS-1 ) { signW = -1; W = PWMSTEPS-1; }
+
+ if ( R < 2 ) { signR = 1; R = 2; }
+ if ( G < 2 ) { signG = 1; G = 2; }
+ if ( B < 2 ) { signB = 1; B = 2; }
+ if ( W < 2 ) { signW = 1; W = 2; }
+
+ pwm_set_duty( CIEL8[R], 0 );
+ pwm_set_duty( CIEL8[G], 1 );
+ pwm_set_duty( CIEL8[B], 2 );
+ pwm_set_duty( CIEL8[W], 3 );
+
+ pwm_start(); // commit
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 Stefan Brüns <stefan.bruens@rwth-aachen.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Set the following three defines to your needs */
+
+#ifndef SDK_PWM_PERIOD_COMPAT_MODE
+ #define SDK_PWM_PERIOD_COMPAT_MODE 0
+#endif
+#ifndef PWM_MAX_CHANNELS
+ #define PWM_MAX_CHANNELS 8
+#endif
+#define PWM_DEBUG 0
+#define PWM_USE_NMI 0
+
+/* no user servicable parts beyond this point */
+
+#define PWM_MAX_TICKS 0x7fffff
+#if SDK_PWM_PERIOD_COMPAT_MODE
+#define PWM_PERIOD_TO_TICKS(x) (x * 0.2)
+#define PWM_DUTY_TO_TICKS(x) (x * 5)
+#define PWM_MAX_DUTY (PWM_MAX_TICKS * 0.2)
+#define PWM_MAX_PERIOD (PWM_MAX_TICKS * 5)
+#else
+#define PWM_PERIOD_TO_TICKS(x) (x)
+#define PWM_DUTY_TO_TICKS(x) (x)
+#define PWM_MAX_DUTY PWM_MAX_TICKS
+#define PWM_MAX_PERIOD PWM_MAX_TICKS
+#endif
+
+#include <c_types.h>
+#include <pwm.h>
+#include <eagle_soc.h>
+#include <ets_sys.h>
+
+// from SDK hw_timer.c
+#define TIMER1_DIVIDE_BY_16 0x0004
+#define TIMER1_ENABLE_TIMER 0x0080
+
+struct pwm_phase {
+ uint32_t ticks; ///< delay until next phase, in 200ns units
+ uint16_t on_mask; ///< GPIO mask to switch on
+ uint16_t off_mask; ///< GPIO mask to switch off
+};
+
+/* Three sets of PWM phases, the active one, the one used
+ * starting with the next cycle, and the one updated
+ * by pwm_start. After the update pwm_next_set
+ * is set to the last updated set. pwm_current_set is set to
+ * pwm_next_set from the interrupt routine during the first
+ * pwm phase
+ */
+typedef struct pwm_phase (pwm_phase_array)[PWM_MAX_CHANNELS + 2];
+static pwm_phase_array pwm_phases[3];
+static struct {
+ struct pwm_phase* next_set;
+ struct pwm_phase* current_set;
+ uint8_t current_phase;
+} pwm_state;
+
+static uint32_t pwm_period;
+static uint32_t pwm_period_ticks;
+static uint32_t pwm_duty[PWM_MAX_CHANNELS];
+static uint16_t gpio_mask[PWM_MAX_CHANNELS];
+static uint8_t pwm_channels;
+
+// 3-tuples of MUX_REGISTER, MUX_VALUE and GPIO number
+typedef uint32_t (pin_info_type)[3];
+
+struct gpio_regs {
+ uint32_t out; /* 0x60000300 */
+ uint32_t out_w1ts; /* 0x60000304 */
+ uint32_t out_w1tc; /* 0x60000308 */
+ uint32_t enable; /* 0x6000030C */
+ uint32_t enable_w1ts; /* 0x60000310 */
+ uint32_t enable_w1tc; /* 0x60000314 */
+ uint32_t in; /* 0x60000318 */
+ uint32_t status; /* 0x6000031C */
+ uint32_t status_w1ts; /* 0x60000320 */
+ uint32_t status_w1tc; /* 0x60000324 */
+};
+static struct gpio_regs* gpio = (struct gpio_regs*)(0x60000300);
+
+struct timer_regs {
+ uint32_t frc1_load; /* 0x60000600 */
+ uint32_t frc1_count; /* 0x60000604 */
+ uint32_t frc1_ctrl; /* 0x60000608 */
+ uint32_t frc1_int; /* 0x6000060C */
+ uint8_t pad[16];
+ uint32_t frc2_load; /* 0x60000620 */
+ uint32_t frc2_count; /* 0x60000624 */
+ uint32_t frc2_ctrl; /* 0x60000628 */
+ uint32_t frc2_int; /* 0x6000062C */
+ uint32_t frc2_alarm; /* 0x60000630 */
+};
+static struct timer_regs* timer = (struct timer_regs*)(0x60000600);
+
+static void ICACHE_RAM_ATTR
+pwm_intr_handler(void)
+{
+ if ((pwm_state.current_set[pwm_state.current_phase].off_mask == 0) &&
+ (pwm_state.current_set[pwm_state.current_phase].on_mask == 0)) {
+ pwm_state.current_set = pwm_state.next_set;
+ pwm_state.current_phase = 0;
+ }
+
+ do {
+ // force write to GPIO registers on each loop
+ asm volatile ("" : : : "memory");
+
+ gpio->out_w1ts = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].on_mask);
+ gpio->out_w1tc = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].off_mask);
+
+ uint32_t ticks = pwm_state.current_set[pwm_state.current_phase].ticks;
+
+ pwm_state.current_phase++;
+
+ if (ticks) {
+ if (ticks >= 16) {
+ // constant interrupt overhead
+ ticks -= 9;
+ timer->frc1_int &= ~FRC1_INT_CLR_MASK;
+ WRITE_PERI_REG(&timer->frc1_load, ticks);
+ return;
+ }
+
+ ticks *= 4;
+ do {
+ ticks -= 1;
+ // stop compiler from optimizing delay loop to noop
+ asm volatile ("" : : : "memory");
+ } while (ticks > 0);
+ }
+
+ } while (1);
+}
+
+/**
+ * period: initial period (base unit 1us OR 200ns)
+ * duty: array of initial duty values, may be NULL, may be freed after pwm_init
+ * pwm_channel_num: number of channels to use
+ * pin_info_list: array of pin_info
+ */
+void ICACHE_FLASH_ATTR
+pwm_init(uint32_t period, uint32_t *duty, uint32_t pwm_channel_num,
+ uint32_t (*pin_info_list)[3])
+{
+ int i, j, n;
+
+ pwm_channels = pwm_channel_num;
+ if (pwm_channels > PWM_MAX_CHANNELS)
+ pwm_channels = PWM_MAX_CHANNELS;
+
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < (PWM_MAX_CHANNELS + 2); j++) {
+ pwm_phases[i][j].ticks = 0;
+ pwm_phases[i][j].on_mask = 0;
+ pwm_phases[i][j].off_mask = 0;
+ }
+ }
+ pwm_state.current_set = pwm_state.next_set = 0;
+ pwm_state.current_phase = 0;
+
+ uint32_t all = 0;
+ // PIN info: MUX-Register, Mux-Setting, PIN-Nr
+ for (n = 0; n < pwm_channels; n++) {
+ pin_info_type* pin_info = &pin_info_list[n];
+ PIN_FUNC_SELECT((*pin_info)[0], (*pin_info)[1]);
+ gpio_mask[n] = 1 << (*pin_info)[2];
+ all |= 1 << (*pin_info)[2];
+ if (duty)
+ pwm_set_duty(duty[n], n);
+ }
+ GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, all);
+ GPIO_REG_WRITE(GPIO_ENABLE_W1TS_ADDRESS, all);
+
+ pwm_set_period(period);
+
+#if PWM_USE_NMI
+ ETS_FRC_TIMER1_NMI_INTR_ATTACH(pwm_intr_handler);
+#else
+ ETS_FRC_TIMER1_INTR_ATTACH(pwm_intr_handler, NULL);
+#endif
+ TM1_EDGE_INT_ENABLE();
+
+ timer->frc1_int &= ~FRC1_INT_CLR_MASK;
+ timer->frc1_ctrl = 0;
+
+ pwm_start();
+}
+
+__attribute__ ((noinline))
+static uint8_t ICACHE_FLASH_ATTR
+_pwm_phases_prep(struct pwm_phase* pwm)
+{
+ uint8_t n, phases;
+
+ uint16_t off_mask = 0;
+ for (n = 0; n < pwm_channels + 2; n++) {
+ pwm[n].ticks = 0;
+ pwm[n].on_mask = 0;
+ pwm[n].off_mask = 0;
+ }
+ phases = 1;
+ for (n = 0; n < pwm_channels; n++) {
+ uint32_t ticks = PWM_DUTY_TO_TICKS(pwm_duty[n]);
+ if (ticks == 0) {
+ pwm[0].off_mask |= gpio_mask[n];
+ } else if (ticks >= pwm_period_ticks) {
+ pwm[0].on_mask |= gpio_mask[n];
+ } else {
+ if (ticks < (pwm_period_ticks/2)) {
+ pwm[phases].ticks = ticks;
+ pwm[0].on_mask |= gpio_mask[n];
+ pwm[phases].off_mask = gpio_mask[n];
+ } else {
+ pwm[phases].ticks = pwm_period_ticks - ticks;
+ pwm[phases].on_mask = gpio_mask[n];
+ pwm[0].off_mask |= gpio_mask[n];
+ }
+ phases++;
+ }
+ }
+ pwm[phases].ticks = pwm_period_ticks;
+
+ // bubble sort, lowest to hightest duty
+ n = 2;
+ while (n < phases) {
+ if (pwm[n].ticks < pwm[n - 1].ticks) {
+ struct pwm_phase t = pwm[n];
+ pwm[n] = pwm[n - 1];
+ pwm[n - 1] = t;
+ if (n > 2)
+ n--;
+ } else {
+ n++;
+ }
+ }
+
+#if PWM_DEBUG
+ int t = 0;
+ for (t = 0; t <= phases; t++) {
+ ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
+ }
+#endif
+
+ // shift left to align right edge;
+ uint8_t l = 0, r = 1;
+ while (r <= phases) {
+ uint32_t diff = pwm[r].ticks - pwm[l].ticks;
+ if (diff && (diff <= 16)) {
+ uint16_t mask = pwm[r].on_mask | pwm[r].off_mask;
+ pwm[l].off_mask ^= pwm[r].off_mask;
+ pwm[l].on_mask ^= pwm[r].on_mask;
+ pwm[0].off_mask ^= pwm[r].on_mask;
+ pwm[0].on_mask ^= pwm[r].off_mask;
+ pwm[r].ticks = pwm_period_ticks - diff;
+ pwm[r].on_mask ^= mask;
+ pwm[r].off_mask ^= mask;
+ } else {
+ l = r;
+ }
+ r++;
+ }
+
+#if PWM_DEBUG
+ for (t = 0; t <= phases; t++) {
+ ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
+ }
+#endif
+
+ // sort again
+ n = 2;
+ while (n <= phases) {
+ if (pwm[n].ticks < pwm[n - 1].ticks) {
+ struct pwm_phase t = pwm[n];
+ pwm[n] = pwm[n - 1];
+ pwm[n - 1] = t;
+ if (n > 2)
+ n--;
+ } else {
+ n++;
+ }
+ }
+
+ // merge same duty
+ l = 0, r = 1;
+ while (r <= phases) {
+ if (pwm[r].ticks == pwm[l].ticks) {
+ pwm[l].off_mask |= pwm[r].off_mask;
+ pwm[l].on_mask |= pwm[r].on_mask;
+ pwm[r].on_mask = 0;
+ pwm[r].off_mask = 0;
+ } else {
+ l++;
+ if (l != r) {
+ struct pwm_phase t = pwm[l];
+ pwm[l] = pwm[r];
+ pwm[r] = t;
+ }
+ }
+ r++;
+ }
+ phases = l;
+
+#if PWM_DEBUG
+ for (t = 0; t <= phases; t++) {
+ ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
+ }
+#endif
+
+ // transform absolute end time to phase durations
+ for (n = 0; n < phases; n++) {
+ pwm[n].ticks =
+ pwm[n + 1].ticks - pwm[n].ticks;
+ // subtract common overhead
+ pwm[n].ticks--;
+ }
+ pwm[phases].ticks = 0;
+
+ // do a cyclic shift if last phase is short
+ if (pwm[phases - 1].ticks < 16) {
+ for (n = 0; n < phases - 1; n++) {
+ struct pwm_phase t = pwm[n];
+ pwm[n] = pwm[n + 1];
+ pwm[n + 1] = t;
+ }
+ }
+
+#if PWM_DEBUG
+ for (t = 0; t <= phases; t++) {
+ ets_printf("%d +%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
+ }
+ ets_printf("\n");
+#endif
+
+ return phases;
+}
+
+void ICACHE_FLASH_ATTR
+pwm_start(void)
+{
+ pwm_phase_array* pwm = &pwm_phases[0];
+
+ if ((*pwm == pwm_state.next_set) ||
+ (*pwm == pwm_state.current_set))
+ pwm++;
+ if ((*pwm == pwm_state.next_set) ||
+ (*pwm == pwm_state.current_set))
+ pwm++;
+
+ uint8_t phases = _pwm_phases_prep(*pwm);
+
+ // all with 0% / 100% duty - stop timer
+ if (phases == 1) {
+ if (pwm_state.next_set) {
+#if PWM_DEBUG
+ ets_printf("PWM stop\n");
+#endif
+ timer->frc1_ctrl = 0;
+ ETS_FRC1_INTR_DISABLE();
+ }
+ pwm_state.next_set = NULL;
+
+ GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, (*pwm)[0].on_mask);
+ GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, (*pwm)[0].off_mask);
+
+ return;
+ }
+
+ // start if not running
+ if (!pwm_state.next_set) {
+#if PWM_DEBUG
+ ets_printf("PWM start\n");
+#endif
+ pwm_state.current_set = pwm_state.next_set = *pwm;
+ pwm_state.current_phase = phases - 1;
+ ETS_FRC1_INTR_ENABLE();
+ RTC_REG_WRITE(FRC1_LOAD_ADDRESS, 0);
+ timer->frc1_ctrl = TIMER1_DIVIDE_BY_16 | TIMER1_ENABLE_TIMER;
+ return;
+ }
+
+ pwm_state.next_set = *pwm;
+}
+
+void ICACHE_FLASH_ATTR
+pwm_set_duty(uint32_t duty, uint8_t channel)
+{
+ if (channel > PWM_MAX_CHANNELS)
+ return;
+
+ if (duty > PWM_MAX_DUTY)
+ duty = PWM_MAX_DUTY;
+
+ pwm_duty[channel] = duty;
+}
+
+uint32_t ICACHE_FLASH_ATTR
+pwm_get_duty(uint8_t channel)
+{
+ if (channel > PWM_MAX_CHANNELS)
+ return 0;
+ return pwm_duty[channel];
+}
+
+void ICACHE_FLASH_ATTR
+pwm_set_period(uint32_t period)
+{
+ pwm_period = period;
+
+ if (pwm_period > PWM_MAX_PERIOD)
+ pwm_period = PWM_MAX_PERIOD;
+
+ pwm_period_ticks = PWM_PERIOD_TO_TICKS(period);
+}
+
+uint32_t ICACHE_FLASH_ATTR
+pwm_get_period(void)
+{
+ return pwm_period;
+}
+
+uint32_t ICACHE_FLASH_ATTR
+get_pwm_version(void)
+{
+ return 1;
+}
+
+void ICACHE_FLASH_ATTR
+set_pwm_debug_en(uint8_t print_en)
+{
+ (void) print_en;
+}
+
--- /dev/null
+#!/bin/bash
+
+if [ $# -lt 1 ]; then
+ echo "Usage: $0 <sketch name> [upload]"
+ exit 0
+fi
+
+# assign WIFI_SSID and WIFI_PASS in external file ../info
+# source "../info"
+
+SKETCH="$1/$1.ino"
+
+TMP="/tmp/ESPcompile.tmp"
+
+MAKE_FILE="~/Bin/SDK/ESP/makeEspArduino/makeEspArduino.mk"
+ESP_SDK_ROOT=~/Bin/SDK/arduino-1.8.5/hardware/esp8266com/esp8266 # keep it without quotation marks
+
+# nodemcuv2, generic, esp8285
+# F_CPU=80000000L
+# BUILD_EXTRA_FLAGS="-DIM_WIFI_SSID=\"$WIFI_SSID\" -DIM_WIFI_PASS=\"$WIFI_PASS\""
+# make clean -f "$MAKE_FILE" ESP_ROOT=$ESP_SDK_ROOT F_CPU=80000000L CHIP=esp8266 BOARD=esp8285 SKETCH="$SKETCH" $2
+stdbuf -oL make -f "$MAKE_FILE" ESP_ROOT=$ESP_SDK_ROOT F_CPU=80000000L CHIP=esp8266 BOARD=esp8285 SKETCH="$SKETCH" $2 2>&1 | tee "$TMP"
+
+if [ -s "$TMP" ]; then
+ binSRC=$( cat "$TMP" | grep Linking | sed -e 's/Linking //g' )
+ cp "$binSRC" .
+
+ serialPort=$(cat "$TMP" | grep "opening port" | awk '{split($0,a," "); print a[3]}')
+else
+ serialPort=/dev/ttyUSB0
+fi
+
+echo $serialPort
+
+exit 0
+
+
+
+
+sleep 1
+
+if cat "$TMP" | grep -q 'error'; then
+ echo "exit"
+else
+ if [ "$1" != "noserial" ]; then
+
+ echo "Connecting $serialPort"
+
+ while [ 1 ]; do
+ cat $serialPort
+ sleep 1
+ done
+ else
+ echo "Serial terminal omited"
+ fi
+fi
+
+rm "$TMP"